using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace Taco.Timeline
{
    [RequireComponent(typeof(Animator))]
    public class TimelinePlayer : MonoBehaviour
    {
        public RuntimeAnimatorController Controller;

        [ObjectField("TestTimeline")]
        public Timeline TimelineField;

        bool m_IsPlaying;
        public bool IsPlaying 
        {
            get => m_IsPlaying;
            set
            {
                m_IsPlaying = value;
#if UNITY_EDITOR
                if (!Application.isPlaying)
                {
                    if (m_IsPlaying)
                    {
                        UnityEditor.EditorApplication.update += EditorUpdate;
                    }
                    else
                    {
                        UnityEditor.EditorApplication.update -= EditorUpdate;
                    }
                }
#endif
            }
        }
        public bool IsValid => PlayableGraph.IsValid();
        public PlayableGraph PlayableGraph { get; private set; }
        public AnimationLayerMixerPlayable RootPlayable { get; private set; }
        public AnimatorControllerPlayable CtrlPlayable { get; private set; }
        public Queue<int> EmptyInputs { get; private set; }
        public List<Timeline> RunningTimelines { get; private set; }

        public float AdditionalDelta;

        private void OnEnable()
        {
            Init();
            IsPlaying = true;
        }
        private void OnDisable()
        {
            Dispose();
        }
        private void Update()
        {
            if (IsPlaying)
            {
                Evaluate(Time.deltaTime);
                if (AdditionalDelta > 0)
                {
                    Evaluate(AdditionalDelta);
                    AdditionalDelta = 0;
                }
            }
        }

        public void Evaluate(float deltaTime)
        {
            foreach (var runningTimelines in RunningTimelines)
            {
                runningTimelines.Time += deltaTime;
            }
            PlayableGraph.Evaluate(deltaTime);
        }

        public void Init()
        {
            PlayableGraph = PlayableGraph.Create("Taco.Timeline.PlayableGraph");
            RootPlayable = AnimationLayerMixerPlayable.Create(PlayableGraph);

            var playableOutput = AnimationPlayableOutput.Create(PlayableGraph, "Animation", GetComponent<Animator>());
            playableOutput.SetSourcePlayable(RootPlayable);

            CtrlPlayable = AnimatorControllerPlayable.Create(PlayableGraph, Controller);
            RootPlayable.AddInput(CtrlPlayable, 0, 1);

            RunningTimelines = new List<Timeline>();
            EmptyInputs = new Queue<int>();
        }
        public void Dispose()
        {
            if (IsValid)
            {
                for (int i = RunningTimelines.Count - 1; i >= 0; i--)
                {
                    RemoveTimeline(RunningTimelines[i]);
                }
                PlayableGraph.Destroy();
            }
            RunningTimelines = null;
            EmptyInputs = null;
            IsPlaying = false;
        }

        public void TestTimeline(Object newValue)
        {
            if(IsValid && newValue is Timeline timeline && !RunningTimelines.Contains(timeline))
            {
                AddTimeline(timeline);
                timeline.OnDone += () => Debug.Log("Done");
            }
            else if (IsValid && newValue is null && RunningTimelines.Contains(TimelineField))
            {
                RemoveTimeline(RunningTimelines[RunningTimelines.Count - 1]);
            }
        }
        public void AddTimeline(Timeline timeline)
        {
            int moreRequiredInputs = timeline.Tracks.Count - EmptyInputs.Count;
            if (moreRequiredInputs > 0)
            {
                int oldCount = RootPlayable.GetInputCount();
                RootPlayable.SetInputCount(oldCount + moreRequiredInputs);
                for (int i = oldCount; i < oldCount + moreRequiredInputs; i++)
                {
                    timeline.EmptyInputs.Enqueue(i);
                }
                for (int i = 0; i < EmptyInputs.Count; i++)
                {
                    timeline.EmptyInputs.Enqueue(EmptyInputs.Dequeue());
                }
            }
            else
            {
                for (int i = 0; i < timeline.Tracks.Count; i++)
                {
                    timeline.EmptyInputs.Enqueue(EmptyInputs.Dequeue());
                }
            }

            timeline.Init();
            timeline.Bind(this);
            RunningTimelines.Add(timeline);
        }
        public void RemoveTimeline(Timeline timeline)
        {
            foreach (var track in timeline.Tracks)
            {
                EmptyInputs.Enqueue(track.PlayableIndex);
            }

            timeline.Unbind();
            RunningTimelines.Remove(timeline);
        }

#if UNITY_EDITOR

        public void EditorUpdate()
        {
            Evaluate((float)Editor.TacoEditorUtility.DeltaTime);
            if (AdditionalDelta > 0)
            {
                Evaluate(AdditionalDelta);
                AdditionalDelta = 0;
            }
        }
#endif
    }
}